package com.lsa.expression.parser.poc;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import com.lsa.expression.parser.ParseException;
import com.lsa.expression.tree.ExprNode;
import com.lsa.expression.tree.FactorNode;
import com.lsa.expression.tree.Node;
import com.lsa.expression.tree.OperatorNode;
import com.lsa.expression.tree.TermNode;
import com.lsa.expression.utils.NodeAndPosition;

public class LLPocParser
{
    Set<String> priorOneOps = new HashSet<String>(Arrays.asList("+", "-"));
    Set<String> priorTwoOps = new HashSet<String>(Arrays.asList("*", "/"));
    
    public Node parse(String input) throws ParseException
    {
        return getExpr(input, 0, null).node;
    }
    
    protected NodeAndPosition getExpr(String input, int pos, Node parent) throws ParseException
    {
        Node expr = new ExprNode(parent);
        NodeAndPosition nodePos = new NodeAndPosition(expr, 0);      
        
        NodeAndPosition nextNodePos = getTerm(input, pos, expr);
        nodePos.position = nextNodePos.position;
        
        if (nextNodePos.position < input.length() - 1)
        {
            String operator = input.substring(nextNodePos.position, nextNodePos.position + 1);
            while(priorOneOps.contains(operator))
            {
                new OperatorNode(expr, operator);
                nextNodePos = getTerm(input, nextNodePos.position + operator.length(), expr);
                
                nodePos.position = nextNodePos.position;         
                if (nextNodePos.position < input.length() - 1)
                    operator = input.substring(nextNodePos.position, nextNodePos.position + 1);
                else
                    operator = "";
            }
        }
        
        return nodePos;
    }
    
    protected NodeAndPosition getTerm(String input, int pos, Node parent) throws ParseException
    {
        Node term = new TermNode(parent);
        NodeAndPosition nodePos = new NodeAndPosition(term, 0);
        
        NodeAndPosition nextNodePos = getFact(input, pos, term);
        nodePos.position = nextNodePos.position;
        
        if (nextNodePos.position < input.length() - 1)
        {
            String operator = input.substring(nextNodePos.position, nextNodePos.position + 1);
            while(priorTwoOps.contains(operator))
            {
                new OperatorNode(term, operator);
                nextNodePos = getFact(input, nextNodePos.position + operator.length(), term);            
                nodePos.position = nextNodePos.position;            
                
                if (nextNodePos.position < input.length() - 1)
                    operator = input.substring(nextNodePos.position, nextNodePos.position + 1);
                else
                    operator = "";
            }
        }
        
        return nodePos;
    }
    
    protected NodeAndPosition getFact(String input, int pos, Node parent) throws ParseException
    {
        FactorNode fact = new FactorNode(parent);
        NodeAndPosition nodePos = new NodeAndPosition(fact, 0);
        
        Character currChar =  input.charAt(pos);
        if (Character.isDigit(currChar))
        {
            StringBuffer numberLiteralSB = new StringBuffer();
            numberLiteralSB.append(input.charAt(pos));
            int i = pos;
            while( i < (input.length() - 1) && Character.isDigit(input.charAt(++i)))
            {                
                numberLiteralSB.append(input.charAt(i));
            }
            
            fact.setData(numberLiteralSB.toString());
            nodePos.position = i;
        }
        else if (currChar.equals('('))
        {
            NodeAndPosition nextNodePos = getExpr(input, pos + 1, fact);
            if (input.charAt(nextNodePos.position) != ')')
            {
                throw new ParseException(String.format("Expected ')' at position %d. Input: %s",  pos, input.substring(pos)));
            }
            nodePos.position = nextNodePos.position + 1;
        }
        else
        {
            throw new ParseException(String.format("Expected '(' or number literal at position %i. Input: %s", pos, input.substring(pos)));
        }
        
        return nodePos;
    }
}
